package drds.common.db.xml_mapping.script.xml_tags;

import drds.common.db.xml_mapping.builder.BaseBuilder;
import drds.common.db.xml_mapping.builder.BuilderException;
import drds.common.db.xml_mapping.mapping.SqlSource;
import drds.common.db.xml_mapping.parsing.XmlNode;
import drds.common.db.xml_mapping.script.defaults.RawSqlSource;
import drds.common.db.xml_mapping.session.Configuration;
import org.w3c.dom.NodeList;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


public class XMLScriptBuilder extends BaseBuilder {

  private final XmlNode xmlNode;
  private final Class<?> parameterType;
  private final Map<String, XmlNodeHandler> tagToXmlNodeHandlerMap = new HashMap<>();
  private boolean isDynamic;

  public XMLScriptBuilder(Configuration configuration, XmlNode xmlNode) {
    this(configuration, xmlNode, null);
  }

  public XMLScriptBuilder(Configuration configuration, XmlNode xmlNode, Class<?> parameterType) {
    super(configuration);
    this.xmlNode = xmlNode;
    this.parameterType = parameterType;
    initTagToXmlNodeHandlerMap();
  }


  private void initTagToXmlNodeHandlerMap() {
    tagToXmlNodeHandlerMap.put("trim", new TrimHandler());
    tagToXmlNodeHandlerMap.put("set", new SetHandler());
    tagToXmlNodeHandlerMap.put("where", new WhereHandler());
    tagToXmlNodeHandlerMap.put("foreach", new ForEachHandler());
    tagToXmlNodeHandlerMap.put("if", new IfHandler());
    //
    tagToXmlNodeHandlerMap.put("choose", new ChooseHandler());
    tagToXmlNodeHandlerMap.put("when", new IfHandler());
    tagToXmlNodeHandlerMap.put("otherwise", new OtherwiseHandler());
    //
    tagToXmlNodeHandlerMap.put("bind", new BindHandler());
  }

  public SqlSource parseScriptNode() {
    Mixed rootSqlNode = parseDynamicTags(xmlNode);
    SqlSource sqlSource;
    if (isDynamic) {
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
  }

  protected Mixed parseDynamicTags(XmlNode xmlNode) {
    List<DynamicNode> contents = new ArrayList<>();
    NodeList children = xmlNode.getNode().getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
      XmlNode child = xmlNode.newXmlNode(children.item(i));
      if (child.getNode().getNodeType() == org.w3c.dom.Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == org.w3c.dom.Node.TEXT_NODE) {
        String data = child.getStringBody("");
        Text textSqlNode = new Text(data);
        if (textSqlNode.isDynamic()) {
          contents.add(textSqlNode);
          isDynamic = true;
        } else {
          contents.add(new StaticText(data));
        }
      } else if (child.getNode().getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) { // issue #628
        String nodeName = child.getNode().getNodeName();
        XmlNodeHandler handler = tagToXmlNodeHandlerMap.get(nodeName);
        if (handler == null) {
          throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
        }
        handler.handleNode(child, contents);
        isDynamic = true;
      }
    }
    return new Mixed(contents);
  }

  private interface XmlNodeHandler {
    void handleNode(XmlNode xmlNodeToHandle, List<DynamicNode> targetContents);
  }

  private class BindHandler implements XmlNodeHandler {
    public BindHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XmlNode xmlNodeToHandle, List<DynamicNode> targetContents) {
      final String name = xmlNodeToHandle.getStringAttribute("name");
      final String expression = xmlNodeToHandle.getStringAttribute("value");
      final VarDecl node = new VarDecl(name, expression);
      targetContents.add(node);
    }
  }

  private class TrimHandler implements XmlNodeHandler {
    public TrimHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XmlNode xmlNode, List<DynamicNode> targetContents) {
      Mixed mixedSqlNode = parseDynamicTags(xmlNode);
      String prefix = xmlNode.getStringAttribute("prefix");
      String prefixOverrides = xmlNode.getStringAttribute("prefixOverrides");
      String suffix = xmlNode.getStringAttribute("suffix");
      String suffixOverrides = xmlNode.getStringAttribute("suffixOverrides");
      Trim trim = new Trim(configuration, mixedSqlNode, prefix, prefixOverrides, suffix, suffixOverrides);
      targetContents.add(trim);
    }
  }

  private class WhereHandler implements XmlNodeHandler {
    public WhereHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XmlNode xmlNodeToHandle, List<DynamicNode> targetContents) {
      Mixed mixedSqlNode = parseDynamicTags(xmlNodeToHandle);
      Where where = new Where(configuration, mixedSqlNode);
      targetContents.add(where);
    }
  }

  private class SetHandler implements XmlNodeHandler {
    public SetHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XmlNode xmlNodeToHandle, List<DynamicNode> targetContents) {
      Mixed mixedSqlNode = parseDynamicTags(xmlNodeToHandle);
      Set set = new Set(configuration, mixedSqlNode);
      targetContents.add(set);
    }
  }

  private class ForEachHandler implements XmlNodeHandler {
    public ForEachHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XmlNode xmlNodeToHandle, List<DynamicNode> targetContents) {
      Mixed mixedSqlNode = parseDynamicTags(xmlNodeToHandle);
      String collection = xmlNodeToHandle.getStringAttribute("collection");
      String item = xmlNodeToHandle.getStringAttribute("item");
      String index = xmlNodeToHandle.getStringAttribute("index");
      String open = xmlNodeToHandle.getStringAttribute("open");
      String close = xmlNodeToHandle.getStringAttribute("close");
      String separator = xmlNodeToHandle.getStringAttribute("separator");
      ForEach forEachSqlNode = new ForEach(configuration, mixedSqlNode, collection, index, item, open, close, separator);
      targetContents.add(forEachSqlNode);
    }
  }

  private class IfHandler implements XmlNodeHandler {
    public IfHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XmlNode xmlNodeToHandle, List<DynamicNode> targetContents) {
      Mixed mixedSqlNode = parseDynamicTags(xmlNodeToHandle);
      String test = xmlNodeToHandle.getStringAttribute("test");
      If ifSqlNode = new If(mixedSqlNode, test);
      targetContents.add(ifSqlNode);
    }
  }

  private class OtherwiseHandler implements XmlNodeHandler {
    public OtherwiseHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XmlNode xmlNodeToHandle, List<DynamicNode> targetContents) {
      Mixed mixedSqlNode = parseDynamicTags(xmlNodeToHandle);
      targetContents.add(mixedSqlNode);
    }
  }

  private class ChooseHandler implements XmlNodeHandler {
    public ChooseHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XmlNode xmlNodeToHandle, List<DynamicNode> targetContents) {
      List<DynamicNode> whenNodes = new ArrayList<>();
      List<DynamicNode> otherwiseNodes = new ArrayList<>();
      handleWhenOtherwiseNodes(xmlNodeToHandle, whenNodes, otherwiseNodes);
      DynamicNode defaultNode = getDefaultSqlNode(otherwiseNodes);
      Choose chooseSqlNode = new Choose(whenNodes, defaultNode);
      targetContents.add(chooseSqlNode);
    }

    private void handleWhenOtherwiseNodes(XmlNode chooseSqlXmlNode, List<DynamicNode> ifNodes, List<DynamicNode> defaultNodes) {
      List<XmlNode> children = chooseSqlXmlNode.getChildrenXmlNodeList();
      for (XmlNode child : children) {
        String nodeName = child.getNode().getNodeName();
        XmlNodeHandler handler = tagToXmlNodeHandlerMap.get(nodeName);
        if (handler instanceof IfHandler) {
          handler.handleNode(child, ifNodes);
        } else if (handler instanceof OtherwiseHandler) {
          handler.handleNode(child, defaultNodes);
        }
      }
    }

    private DynamicNode getDefaultSqlNode(List<DynamicNode> defaultNodes) {
      DynamicNode defaultNode = null;
      if (defaultNodes.size() == 1) {
        defaultNode = defaultNodes.get(0);
      } else if (defaultNodes.size() > 1) {
        throw new BuilderException("Too many default (otherwise) elements in choose statement.");
      }
      return defaultNode;
    }
  }

}
